#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Client.h>
#include <Kharlia/Engine/Init.h>
#include <Kharlia/Engine/RenderView.h>
#include <Kharlia/Engine/Engine.h>
#include <Kharlia/Engine/Player.h>
#include <Kharlia/Engine/Actor.h>
#include <Kharlia/Core/IO.h>
#include <Kharlia/Core/App.h>

#include <CEGUIWindowFactory.h>
#include <CEGUIImagesetManager.h>
#include <CEGUISchemeManager.h>
#include <CEGUIWindowManager.h>
#include <CEGUIFontManager.h>
#include <CEGUIWindowFactoryManager.h>
#include <CEGUITplWindowFactory.h>
#include <CEGUIScriptModule.h>
#include <elements/CEGUIEditbox.h>
#include <elements/CEGUIEditboxProperties.h>
#include <elements/CEGUIMultiLineEditbox.h>
#include <falagard/CEGUIFalWidgetLookManager.h>

#include <RendererModules/Direct3D10/CEGUIDirect3D10Renderer.h>
#include <CEGUIExceptions.h>

#define ENABLE_TEST_DIALOG 0
#define ENABLE_MAIN_RENDERVIEW 0
#define ENABLE_SCALING 0
#define ENABLE_CONSOLE 1

namespace kharlia { namespace engine {

using ui::URect;
using ui::UVector2;
using ui::UDim;

//-----------
// FResourceProvider
//-----------

FResourceProvider::FResourceProvider(shared_ptr<core::FLogger> l):
      ui::DefaultResourceProvider(),
      mLogger(l) {}

void FResourceProvider::loadRawDataContainer(ui::String const& filename,
                                             ui::RawDataContainer& output,
                                             ui::String const& resourceGroup) {
    if (core::IsCurrentTaskletMain()) {
        // Default blocking IO if we're on main
        format fmt("Resource provider is loading from the main tasklet: %1%, %2%");
        fmt % filename.c_str() % resourceGroup.c_str();
        mLogger->Warning(fmt);
        ui::DefaultResourceProvider::loadRawDataContainer(filename, output, resourceGroup);
        /*
        if (filename.empty())
            throw ui::InvalidRequestException("kharlia::FResourceProvider::load: "
                "Filename supplied for data loading must be valid");

        ui::String finalFilename(getFinalFilename(filename, resourceGroup));

        using namespace std;
        ifstream file;
        file.open(finalFilename.c_str(), ios::binary | ios::ate);
        if (file.fail())
            throw ui::InvalidRequestException(
                "Kharlia::FResourceProvider::load - " + filename + " does not exist");

        int size = file.tellg();
        file.seekg(0);

        char* buffer = new char[size];

        file.read(buffer, size);
        if (file.gcount() != size) {
            delete[] buffer;
            throw ui::GenericException(
                "kharlia::FResourceProvider::loadRawDataContainer: "
                "A problem occurred while reading file: " + finalFilename);

        }

        output.setData(reinterpret_cast<ui::uint8*>(buffer));
        output.setSize(size);
        */
    }
    else {
        // Adapted DefaultResourceProvider code to stackless
        if (filename.empty()) {
            throw ui::InvalidRequestException(
                "Kharlia::FResourceProvider::load: Filename supplied for data loading must be valid");
        }
        ui::String finalFilename(getFinalFilename(filename, resourceGroup));
        // Create and execute the request
        intrusive_ptr<core::FFileLoadRequest> req(
            new core::FFileLoadRequest(std::string(finalFilename.c_str()), 0, 0)
        );
        core::GetIOManagerPtr()->ExecuteSL(req);
        // Handle an error
        switch (req->GetErrorCode()) {
        case core::IOERR_NONE:
            break;
        case core::IOERR_OPENFAIL:
            throw ui::InvalidRequestException(
                "Kharlia::FResourceProvider::load - " + filename + " does not exist");
        default:
            throw ui::GenericException(
                "Kharlia::FResourceProvider::loadRawDataContainer - Problem reading " + filename);
        }
        // Copy our result into a buffer that CEGUI can use; must be deleted manually
        std::string const& data = req->GetResult();
        //uint8* buffer = reinterpret_cast<uint8*>(malloc(data.size()));
        uint8* buffer = new uint8[data.size()];
        memcpy(buffer, data.c_str(), data.size());
        output.setData(buffer);
        output.setSize(data.size());
    }
}
void FResourceProvider::unloadRawDataContainer(ui::RawDataContainer& data) {
    uint8* ptr = data.getDataPtr();
    delete[] ptr;
    //free(ptr);
    data.setData(nullptr);
    data.setSize(0);
}

//-----------
// CUIScriptModule
//-----------

CUIScriptModule::CUIScriptModule(PyObject* self):
    CObject(self)
{
}

void CUIScriptModule::createBindings() {
    call_method<void>(mSelf, "OnCreateBindings");
}

void CUIScriptModule::destroyBindings() {
    call_method<void>(mSelf, "OnDestroyBindings");
}

bool CUIScriptModule::executeScriptedEventHandler(
    ui::String const& hname,
    ui::EventArgs const& e
    )
{
    return false;
}

ui::Event::Connection CUIScriptModule::subscribeEvent(
    ui::EventSet* target,
    ui::String const& name,
    ui::String const& subname
    )
{
    return ui::Event::Connection();
}

ui::Event::Connection CUIScriptModule::subscribeEvent(
    ui::EventSet* target,
    ui::String const& name,
    ui::Event::Group group,
    ui::String const& subname
    )
{
    return ui::Event::Connection();
}

//-----------
// CClient
//-----------

CClient::CClient(PyObject* self):
    CObject(self),
    mLogger(),
    mInited(false),
    mMousePos(0, 0),
    mCamera(),
    mWindowHandle(0),
    mViewportSize(),
    mTime(0.0),
    mIsLeftArrowPressed(false),
    mIsRightArrowPressed(false),
    mIsUpArrowPressed(false),
    mIsDownArrowPressed(false),

    // Client GUI
    mInitedUI(false),
    mUIFocused(false),
    mUIRenderer(nullptr),
    mUISystem(nullptr),
    mUIResourceProvider(nullptr),
    mUIWindowManager(nullptr),
    mUISchemeManager(nullptr),
    mUIFontManager(nullptr),
    mRenderViews(),
    mDebugText(nullptr),
    mConsole(nullptr),

    // Renderer
    mInitedDX(false),
    mRendering(false),
    mShowActorBounds(false),
    mShowAllActors(false),
    mForceDefaultSprite(false),
    mDeviceContext(0),
    mRenderingContext(0),
    mErrorTexture(nullptr),
    mTilesetTexture(nullptr),
    mDevice(nullptr),
    mRTV(nullptr),
    mSwapChain(nullptr),
    mRS(nullptr),
    mViewMatrixStack(nullptr),
    mProjectionMatrixStack(nullptr),
    mBasicEffect(nullptr),
    mVertexLayout(nullptr),
    mVertexBuffer(nullptr),

    mEditorRenderInfo(nullptr)
{
    for (int i = 0; i < sizeof(mKeyPressTable); i++) {
        mKeyPressTable[i] = false;
    }
}

CClient::~CClient() {
    ReleaseDX();
    assert(!mInited);
}

void CClient::Init(TPtr<CEngine> eng, object window) {
    if (mInited)
        return;
    mEngine = eng;
    assert(eng);
    mLogger = core::FLogger::GetLogger("client");
    mLogger->Info("initializing client");

    // Get the configuration
    // TODO: Expose CApp config directly in C++
    object cr = core::GetAppPtr()->GetSelfAttr("ConfigRoot");
    mConfig = cr["Engine"]["Client"];
    mShowActorBounds = extract<bool>(mConfig["ShowActorBounds"]);

    // Get the window handle
    mWindowHandle = extract<uint32>(window.attr("GetWindowHandle")());
    // Get the viewport size
    mViewportSize = extract<FSizeI>(window.attr("GetSize")());
  
    if (mViewportSize.Width == 0 || mViewportSize.Height == 0)
        throw RuntimeError("Client initialization failed; viewport size not set");
    if (!mWindowHandle || !::IsWindow(reinterpret_cast<HWND>(mWindowHandle)))
        throw RuntimeError("Client initialization failed; invalid window handle");

    try {
        InitDX();
    }
    catch (...) {
        ReleaseDX();
        throw;
    }
    InitUI();

    // Load the testing tileset
    //str ttfn = extract<str>(GetEngine()->GetConfig()["TestTilesetFileName"]);
    //mTilesetTexture = &mUIRenderer->createTexture(PyString_AS_STRING(ttfn.ptr()), "data");

    mViewport = window;
    mInited = true;
    mViewport.attr("OnClientInited")(GetSelf());
}

void CClient::Exit() {
    if (!mViewport.is_none())
        mViewport.attr("OnClientExiting")();

    ExitUI();
    ExitDX();

    mViewport = object();
    mEngine.Reset();
    mConfig = object();
    mLogger.reset();
    mInited = false;
}

void CClient::Tick(float deltaTime) {
    mTime += deltaTime;
    if (mInitedUI)
        mUISystem->injectTimePulse(deltaTime);
    DrawViewport();
}

TPtr<ACamera> CClient::GetRenderCamera() const {
    TPtr<ACamera> cam = mCamera.Lock();
    if (!cam) {
        TPtr<CPlayer> p = GetEngine()->GetLocalPlayer();
        TPtr<CActor> a = p->GetActor();
        if (a && a->GetPlayerController()->GetCamera()) {
            cam = a->GetPlayerController()->GetCamera();
            assert(cam->GetActor());
            assert(!cam->GetTarget() || cam->GetTarget()->GetActor());
            //if (cam->GetTarget() && !cam->GetTarget()->GetActor()) {
            //    GetLoggerPtr()->Warning("Client camera had invalid target");
            //    cam->SetTarget(TPtr<ATransform>());
            //}
        }
    }
    return cam;
}

void CClient::DrawViewport() {
    if (!mInitedDX)
        throw RuntimeError("Have not initialized renderer");
    OnPreRender();
    mRendering = true;
    // Render the scene
    mDevice->ClearRenderTargetView(mRTV, D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f));
    static double renderStart = 0, renderTotal = 0;
    static int renderCount = 0;
    {
        TPtr<ACamera> cam = GetRenderCamera();
        if (cam) {
            assert(cam->GetActor());
            renderStart = core::StatGetClock();
            DrawScene(cam, FRectF(0, 0, float(mViewportSize.Width), float(mViewportSize.Height)));
            renderTotal += core::StatGetClock() - renderStart;
            renderCount++;
        }
    }
    // CEGUI work and rendering
    if (mInitedUI) {
        // FPS Counter
        static double nextup = 0;
        static uint32 frameCount = 0, totalFrameCount = 0;

        frameCount++;
        totalFrameCount++;

        if (mTime >= nextup) {
            // Get proper camera position
            FVector3F pos;
            TPtr<ACamera> cam = mCamera.Lock();
            if (cam && cam->GetTarget()) {
                pos = cam->GetTarget()->GetPosition();
                pos += cam->GetTargetOffset();
            }

            // Calc average scene rendering time
            double renderAvg = renderTotal / renderCount;
            renderTotal = 0;
            renderCount = 0;

            // Format the text to be sent to the window
            static boost::format txt("%1% FPS, RT: %6%, GLErr: %2%, FC: %3%, CamPos: (%4%, %5%)");
            txt % frameCount %
                    0 %
                    totalFrameCount %
                    pos.X %
                    pos.Y %
                    (renderAvg * 1000);
            
            mDebugText->setText(txt.str());
            nextup = mTime + 1.0;
            frameCount = 0;
        }
        // RenderViews are redrawn every frame
        for (uint i = 0; i < mRenderViews.size(); i++) {
            FRenderView* rv = mRenderViews[i];
            if (rv->GetDrawEveryFrame()) {
                rv->invalidate();
            }
        }
        mUISystem->renderGUI();
    }
    // Done rendering
    //SwapBuffers(mDeviceContext);
    mSwapChain->Present(0, 0);
    mRendering = false;
}

bool CClient::ExecImpl(object cmd, object out) {
    // NOTE: Not sure how a permanent static reference will affect cleanup.
    static unicode sClear("clear");

    if (cmd[0] == sClear) {
        ClearConsole();
        return true;
    }

    return false;
}

bool CClient::Exec(object cmd, object out) {
    return call_method<bool>(mSelf, "Exec", cmd, out);
}

ui::MultiLineEditbox* CClient::GetConsoleTextBox() {
    if (mUISystem && mUISystem->getGUISheet()) {
        ui::Window* textboxb = mUISystem->getGUISheet()->getChildRecursive("Console/Frame/Text");
        if (textboxb) {
            ui::MultiLineEditbox* textbox = dynamic_cast<ui::MultiLineEditbox*>(textboxb);
            assert(textbox);
            return textbox;
        }
    }
    return nullptr;
}

void CClient::ClearConsole() {
    ui::MultiLineEditbox* textbox = GetConsoleTextBox();
    if (textbox)
        textbox->setText("Cleared.");
}

void CClient::PrintLine(str text) {
    ui::MultiLineEditbox* textbox = GetConsoleTextBox();
    if (textbox) {
        char const* ctext = extract<char const*>(text);
        textbox->appendText(ctext);
        //textbox->appendText("\n");
    }
}

void CClient::PrintLine(unicode text) {
    PrintLine(extract<str>(text));
}


bool CClient::OnConsoleTextAccepted(ui::EventArgs const& eb) {
    ui::WindowEventArgs const& e = dynamic_cast<ui::WindowEventArgs const&>(eb);
    ui::Editbox& input           = dynamic_cast<ui::Editbox&>(*e.window);

    ui::String const& text = input.getText();
    if (text.size() > 0) {
        str cmd(text.c_str(), text.size());
        input.setText("");
        OnConsoleCommand(cmd);
    }

    return true;
}

namespace {

void EscapeToken(std::string& token) {
    for (int i = token.size() - 1; i >= 0; i--) {
        if (token[i] == '\\') {
            token.replace(i, 1, "");
            if (i > 0 && token[i - 1] == '\\')
                i--;
        }
    }
}

/// Parse a string and divide it into a list of tokens using spaces as delimiters.
/// Also pays attention to single and double quotes.
object ParseTokens(str pys) {
    if (len(pys) == 0)
        throw ValueError("length is zero");

    list tokens;

    std::string s = extract<std::string>(pys);
    std::string::iterator end = s.end(), // end of string
                          tb = end, // token beginning
                          te = end, // token end (after last char)
                          pos = s.begin(); // current position
    char tx = 0; // token end character (double or single quote), not included in token

    while (pos != end) {        
        while (pos != end && *pos == ' ')
            pos++;

        if (pos == end)
            break;

        tb = pos;
        te = end;

        if (*tb == '"' || *tb == '\'') {
            tx = *tb;
            tb = ++pos; // Don't include the quote in the token
            while (pos != end && (*pos != tx || (*(pos - 1) == '\\')))
                pos++;
            te = pos;
            if (pos != end)
                pos++; // Don't include the token ending on the next round
        }
        // Normal behavior
        else {
            // Skip ahead until we hit a space or a null terminator
            while (pos != end && *pos != ' ')
                pos++;
            te = pos;
        }

        // Create a token if our size is greater than zero
        if (tb != te) {
            std::string tokenb(tb, te);
            EscapeToken(tokenb);
            str token(tokenb.c_str(), tokenb.size());
            tokens.append(token);
        }
    }

    if (len(tokens) > 0)
        return tokens;
    return object();
}

} // namespace

bool CClient::OnConsoleCommand(str cmd) {
    mLogger->Debug(str("Console command: ") + cmd);

    object tokens = ParseTokens(cmd);
    if (!tokens.is_none()) {
        mLogger->Debug(str("Command tokens: ") + str(tokens));
        object printfunc = GetSelfAttr("PrintLine");
        if (!GetEngine()->Exec(tokens, printfunc))
            PrintLine("Unknown command.");
    }

    return true;
}

FVector2F CClient::ConvertScreenToTileCoords(FVector2I const& target) {
    TPtr<ACamera> cam = mCamera.Lock();
    if (cam) {
        TPtr<ATransform> camTarget = cam->GetTarget();
        if (camTarget) {
            FVector2F out;

            float vcl = camTarget->GetPosition().X - ((mViewportSize.Width / 2.0f) / 16.0f);
            float vct = camTarget->GetPosition().Y - ((mViewportSize.Height / 2.0f) / 16.0f);

            out.X = vcl + (float(target.X) / 16.0f);
            out.Y = vct + (float(target.Y) / 16.0f);

            return out;
        }
    }
    return FVector2F();
}

void CClient::InputMousePosition(int x, int y) {
    mMousePos.X = x;
    mMousePos.Y = y;
    if (mInitedUI) {
        mUISystem->injectMousePosition(float(x), float(y));
    }
}
void CClient::InputMouseButtonDown(ui::MouseButton b) {
    if (mInitedUI) {
        bool processed = mUISystem->injectMouseButtonDown(b);
        // Check if CEGUI consumed the event
        if (processed) {
            mUIFocused = true;
        }
        else {
            ui::Window* root = mUISystem->getGUISheet();
            ui::Window* active = root->getActiveChild();
            ui::Window* prev = nullptr;
            // Get the active window that is not the GUI sheet
            while (active && active != root) {
                prev = active;
                active = active->getParent();
            }
            active = prev;
            // If the active window wasn't hit, activate root
            if (active && !active->isMousePassThroughEnabled()) {
                if (!active->isHit(ui::Vector2(float(mMousePos.X),
                                               float(mMousePos.Y)))) {
                    active->deactivate();
                    root->activate();
                    mUIFocused = false;
                }
            }
        }
    }
}
void CClient::InputMouseButtonUp(ui::MouseButton b) {
    if (mInitedUI) {
        mUISystem->injectMouseButtonUp(b);
    }
}
void CClient::InputKeyDown(ui::Key::Scan keycode) {
    mKeyPressTable[uint8(keycode)] = true;
    if (mInitedUI) {
        //ui::Window* active = mUISystem->getGUISheet()->getActiveChild();
        mUISystem->injectKeyDown(keycode);
        //UI::Window* w = UISystem->getGUISheet()->getActiveChild();
        //if (w != nullptr) {
        //    std::cout << "ACTIVE CHILD:" << w->getName().c_str() << std::endl;
        //}
        if (mConsole && keycode == ui::Key::Grave) {
            SetConsoleVisible(!GetConsoleVisible());
        }
    }
    GetEngine()->OnKeyDown(keycode);
}

void CClient::InputChar(ui::utf32 codepoint) {
    if (mInitedUI) {
        mUISystem->injectChar(codepoint);
    }
    GetEngine()->OnChar(codepoint);
}

void CClient::InputKeyUp(ui::Key::Scan keycode) {
    mKeyPressTable[uint8(keycode)] = false;
    if (mInitedUI) {
        mUISystem->injectKeyUp(keycode);
    }
    GetEngine()->OnKeyUp(keycode);
}

bool CClient::OnTestButtonClicked(ui::EventArgs const& e) {
    ui::Window* w = mUIWindowManager->getWindow("testButton");
    static int clicks = 0;
    clicks++;
    w->setText((boost::format("Clicks: %1%") % clicks).str());
    return true;
}

bool CClient::OnKeyDown(ui::EventArgs const& e) {
    std::cout << "ROOT KEY DOWN" << std::endl;
    return true;
}
void CClient::OnRenderViewDraw(FRenderView const& view, ui::RenderingContext const& context) {
    //if (view.GetCamera()) {
    //    ui::Rect& pr = view.getOuterRectClipper();
    //    FRectF vp(pr.d_left, pr.d_top, pr.d_right - pr.d_left, pr.d_bottom - pr.d_top);
    //    DrawScene(view.GetCamera(), vp);
    //}
}

void CClient::SetWindowHandle(uint32 handle) {
    mWindowHandle = handle;
    if (mWindowHandle == 0) {
        mLogger->Debug("Set engine window handle to NULL");
    }
}

void CClient::SetViewportSize(FSizeI const& size) {
    if (size == mViewportSize)
        return;
    mLogger->Info(format("requested resize to: %1%, %2%") % size.Width % size.Height);
    //ui::Direct3D10Renderer* ren;
    //if (mInitedUI) {
    //    ren = static_cast<ui::Direct3D10Renderer*>(mUIRenderer);
    //    ren->grabTextures();
    //}
    ResizeDX(size);
    SetDXViewport(size);
    //if (ren) {
    //    ren->restoreTextures();
    //    ren->setDisplaySize(mViewportSize);
    //}
    mViewportSize = size;
    mLogger->Debug("DX parameters have been reset");
}

//void CEngine::SetMouseVisible(bool osstate, bool uistate){
//    if (osstate == true)
//         stuff
//    else 
//         stuff
//    if (uistate == true)
//        UI::MouseCursor::getSingleton().show();
//    else
//        UI::MouseCursor::getSingleton().hide();
//}

void CClient::UnloadTextures() {
    for (uint i = 0; i < mLoadedTextures.size(); i++) {
        shared_ptr<FTexture> tex = mLoadedTextures[i].lock();
        if (!tex)
            continue;
        ui::Texture* rtex = tex->GetIntTexture();
        if (!rtex)
            continue;
        //assert(tex->GetClient().GetPtr() == this);
        tex->SetIntTexture(nullptr);
        if (mUIRenderer)
            mUIRenderer->destroyTexture(*rtex);
    }
    mLoadedTextures.clear();
}

//---------------------------
// CClient UI implementation
//---------------------------

void CClient::InitUI() {
    if (mInitedUI)
        return;
    if (!mInitedDX)
        throw RuntimeError("haven't inited DX");
    mLogger->Debug("initializing CEGUI");
    mUIRenderer = &ui::Direct3D10Renderer::create(mDevice);
    //mUIRenderer = &ui::DirectX10Renderer::create(ui::Size(float(mViewportSize.Width),
    //                                                   float(mViewportSize.Height)));
    mUIResourceProvider = new FResourceProvider(mLogger);
    mUISystem = &ui::System::create(*mUIRenderer, mUIResourceProvider);
    //UI::MouseCursor::getSingleton().hide();
    // Get the default resource provider and set directories
    //mUIResourceProvider = static_cast<UI::DefaultResourceProvider*>(UISystem->getResourceProvider());
    mUIResourceProvider->setResourceGroupDirectory("schemes", "data/gui/schemes/");
    mUIResourceProvider->setResourceGroupDirectory("imagesets", "data/gui/imagesets/");
    mUIResourceProvider->setResourceGroupDirectory("fonts", "data/gui/fonts/");
    mUIResourceProvider->setResourceGroupDirectory("layouts", "data/gui/layouts/");
    mUIResourceProvider->setResourceGroupDirectory("looknfeels", "data/gui/looknfeel/");
    mUIResourceProvider->setResourceGroupDirectory("scripts", "data/scripts/");
    mUIResourceProvider->setResourceGroupDirectory("tilesets", "data/images/tiles/");
    // Specify resource groups for CEGUI subsystems
    ui::Imageset::setDefaultResourceGroup("imagesets");
    ui::Font::setDefaultResourceGroup("fonts");
    ui::Scheme::setDefaultResourceGroup("schemes");
    ui::WidgetLookManager::setDefaultResourceGroup("looknfeels");
    ui::WindowManager::setDefaultResourceGroup("layouts");
    ui::ScriptModule::setDefaultResourceGroup("python_scripts");
    // Get the various managers and load schemes and set defaults
    mUIWindowManager = ui::WindowManager::getSingletonPtr();
    mUISchemeManager = ui::SchemeManager::getSingletonPtr();
    mUIFontManager = ui::FontManager::getSingletonPtr();
    mUISchemeManager->create("TaharezLook.scheme");
    mUISchemeManager->create("VanillaSkin.scheme");
    mUISchemeManager->create("NeoRice.scheme");
    if (!mUIFontManager->isDefined("DejaVuSans-10"))
        mUIFontManager->create("DejaVuSans-10.font");
    //if (!mUIFontManager->isDefined("Commonwealth-10"))
    //    mUIFontManager->create("Commonwealth-10.font");
    //if (!UIFontManager->isFontPresent("Emulator-10"))
    //    UIFontManager->createFont("Emulator.font");
    mUISystem->setDefaultFont("DejaVuSans-10"); // First loaded font is default, this is unnecessary
    mUISystem->setDefaultMouseCursor("Neo-Images", "MouseArrow");
    mUISystem->setDefaultTooltip("TaharezLook/Tooltip");
    ui::WindowFactoryManager::addFactory<ui::TplWindowFactory<FRenderView>>();
    ui::Window* root = mUIWindowManager->createWindow("DefaultWindow", "root");
    // FIX FOR ROOT INPUT: http://www.cegui.org.uk/phpBB2/viewtopic.php?f=10&t=3496
    mUISystem->setGUISheet(root);
#if ENABLE_SCALING
    ui::Imageset* is = ui::ImagesetManager::getSingleton().getImageset("TaharezLook");
    is->setAutoScalingEnabled(false);
    //is->setNativeResolution(UI::Size(float(RnViewportSize.Width), float(RnViewportSize.Height)));
#endif
#if ENABLE_MAIN_RENDERVIEW
    ui::Window* rv = mUIWindowManager->createWindow("Kharlia/RenderView", "MainRenderView");
    //rv->setPosition(UVector2(UDim(0.0f, 0.0f), UDim(0.0f, 0.0f)));
    rv->setSize(UVector2(UDim(1.0f, 0.0f), UDim(1.0f, 0.0f)));
    rv->setEnabled(true);
    rv->setZOrderingEnabled(false);
    root->addChildWindow(rv);
    mRenderViews.push_back(static_cast<FRenderView*>(rv));
#endif
#if ENABLE_TEST_DIALOG
    ui::FrameWindow* fw =
        static_cast<ui::FrameWindow*>(mUIWindowManager->createWindow("TaharezLook/FrameWindow", "testWindow"));
    root->addChildWindow(fw);
    fw->setPosition(ui::UVector2(ui::UDim(0.3f, 0.0f), ui::UDim(0.3f, 0.0f)));
    //fw->setSize(UI::UVector2(UI::UDim(0, 200), UI::UDim(0, 500)));
    fw->setSize(ui::UVector2(ui::UDim(0.0f, 200.0f), ui::UDim(0.0f, 200.0f)));
    fw->setText("Hello World!");

    ui::Window* text = mUIWindowManager->createWindow("TaharezLook/StaticText", "testText");
    fw->addChildWindow(text);
    text->setPosition(ui::UVector2(ui::UDim(0.0f, 15.0f), ui::UDim(0.0f, 40.0f)));
    text->setSize(ui::UVector2(ui::UDim(1.0f, -34.0f), ui::UDim(1.0f, -85.0f)));
    text->setMinSize(ui::UVector2(ui::UDim(0.0f, 0.0f), ui::UDim(0.0f, 32.0f)));
    text->setText("---------------------------------------\n          MOTD: Mido Rocks\n---------------------------------------\n\n\t    There is a lot of work here! Lots and lots of work. Many hours spent pouring over tricky LookNFeelXMLs! And I'm barely half way! Soooo many widgets to fill out and test. Soo many hours ahead of me on the path to something made in CEGUI that people can look upon and not wish they were puking instead of looking upon the wreck that is 99% of available CEGUI interfaces.\n\t    I'm not sure why they didnt include a Nice and Simple UI like what I'm doing here, everything is either TOO lite or TOO bloated. Get it right people!");
    text->setTooltipText("These are the only widgets\nI have done so far. \n All that XML makes me feel yucky! :(");
    text->setProperty( "VertScrollbar", "True");
    text->setProperty( "HorzFormatting", "WordWrapLeftAligned");
    //text->setProperty("TextColours", "tl:FFe6c888 tr:FFe6c888 bl:FFe6c888 br:FFe6c888");
    //text->setProperty("VertFormatting", "VertCentred"); // TopAligned, BottomAligned, VertCentred
    //text->setProperty("HorzFormatting", "HorzCentred"); // LeftAligned, RightAligned, HorzCentred
    text->setProperty("FrameEnabled", "true");

    ui::PushButton* button =
        static_cast<ui::PushButton*>(mUIWindowManager->createWindow("TaharezLook/SystemButton", "testButton"));
    fw->addChildWindow(button);
    button->setPosition(ui::UVector2(ui::UDim(1.0f, -90.0f), ui::UDim(1.0f, -34.0f)));
    button->setSize(ui::UVector2(ui::UDim(0.0f, 80.0f), ui::UDim(0.0f, 24.0f)));
    button->setText("Click me!");
    button->subscribeEvent(
        ui::PushButton::EventClicked, ui::Event::Subscriber(&CEngine::OnTestButtonClicked, this));
#endif
#if ENABLE_CONSOLE
    {
        // Frame
        ui::Window* con = mUIWindowManager->createWindow("TaharezLook/FrameWindow", "Console/Frame");
        con->setText("Console");
        con->setPosition(UVector2(UDim(0, 32), UDim(0, 32)));
        con->setSize(UVector2(UDim(0, 300), UDim(0, 400)));
        con->setVisible(false);
    
        // Text box
        ui::MultiLineEditbox* ctext = dynamic_cast<ui::MultiLineEditbox*>(
            mUIWindowManager->createWindow("TaharezLook/MultiLineEditbox", "Console/Frame/Text"));
        assert(ctext);
        //UI::Window* ctext = UIWindowManager->createWindow("TaharezLook/MultiLineEditbox", "Console/Frame/Text");
        ctext->setText("Ready...");
        ctext->setReadOnly(true);
        ctext->setArea(URect(UDim(0, 12), UDim(0, 12), UDim(1.0f, -12), UDim(1.0f, -38)));
    
        // Input box
        ui::Editbox* cinp = dynamic_cast<ui::Editbox*>(
            mUIWindowManager->createWindow("TaharezLook/Editbox", "Console/Frame/Input"));
        assert(cinp);
        // Window
        //cinp->setText("Console input box");
        //cinp->setArea(URect(UDim(0, 12), UDim(0, 30), UDim(1, -12), UDim(0, 50)));
        //cinp->setArea(URect(UDim(0, 12), UDim(0.9f, 12), UDim(1, -12), UDim(1.0f, -12)));
        cinp->setPosition(UVector2(UDim(0, 6), UDim(1.0f, -30)));
        cinp->setSize(UVector2(UDim(1.0f, -12), UDim(0, 24)));
        cinp->setEnabled(true);
        cinp->setVisible(true);
        // Editbox
        cinp->setReadOnly(false);
        cinp->subscribeEvent(ui::Editbox::EventTextAccepted,
                             ui::SubscriberSlot(&CClient::OnConsoleTextAccepted, this));

        root->addChildWindow(con);
        con->addChildWindow(ctext);
        con->addChildWindow(cinp);
        mConsole = con;
    }
#endif
    ui::Window *debug_text = mUIWindowManager->createWindow("Vanilla/StaticText", "debugText");
    root->addChildWindow(debug_text);
    debug_text->setPosition(UVector2(ui::UDim(0.0125f, 0.0f), UDim(0.0f, 0.0f)));
    debug_text->setSize(UVector2(ui::UDim(1.0f, 0.0f), UDim(0.0f, 32.0f)));
    debug_text->setMinSize(UVector2(ui::UDim(0.0f, 0.0f), UDim(0.0f, 32.0f)));
    debug_text->setText("...");
    debug_text->setProperty("TextColours", "tl:FFFFFFFF tr:FFFFFFFF bl:FFFFFFFF br:FFFFFFFF");
    debug_text->setProperty("VertFormatting", "VertCentred"); // TopAligned, BottomAligned, VertCentred
    debug_text->setProperty("HorzFormatting", "LeftAligned"); // LeftAligned, RightAligned, HorzCentred
    debug_text->setProperty("FrameEnabled", "false");
    debug_text->setProperty("BackgroundEnabled","false");
    
    //debug_text->subscribeEvent("test", ui::SubscriberSlot(make_function(&CClient::OnConsoleTextAccepted)));
    //debug_text->subscribeEvent("test", ui::SubscriberSlot(&CClient::OnConsoleTextAccepted, this));
    mDebugText = debug_text;
    // Temporary actor texture
    str datapath = extract<str>(GetEngine()->GetConfig()["DataPath"]);
    mUIResourceProvider->setResourceGroupDirectory("images", "data/images/");
    mUIResourceProvider->setResourceGroupDirectory("data", PyString_AS_STRING(datapath.ptr()));
    mUIResourceProvider->setResourceGroupDirectory("none", "");
    object apath = GetEngine()->ResolvePath(str("images/actor.png"));
    if (apath.is_none())
        throw RuntimeError("images/actor.png not found");
    str apathc = extract<str>(apath);
    TempActorTexture = &mUIRenderer->createTexture(PyString_AS_STRING(apathc.ptr()), "none");
    TempActorTextureR.reset(new FTexture("images/actor.png"));
    //TempActorTextureR->SetClient(GetSelfPtr<CClient>());
    TempActorTextureR->SetIntTexture(TempActorTexture);
    mLoadedTextures.push_back(TempActorTextureR);
    GetEngine()->GetResourceManager()->Register(TempActorTextureR);

    mInitedUI = true;
}
void CClient::ExitUI() {
    //if (!mInitedUI)
    //    return;
    mLogger->Debug("exiting CEGUI");

    // Notify interested objects that UI is existing. This is where
    // properly written objects should clear any references to
    // CEGUI objects.
    OnUIExiting();

    // Unload all CEGUI textures tied to FTexture instances
    UnloadTextures();
    if (TempActorTextureR) {
        assert(TempActorTextureR->GetState() == RS_UNLOADED);
        TempActorTextureR.reset();
    }

    mInitedUI = false;

    TempActorTexture = nullptr;
    mRenderViews.clear();
    mDebugText = nullptr;

    if (mUISystem)
        ui::System::destroy();
    if (mUIRenderer)
        ui::Direct3D10Renderer::destroy(*static_cast<ui::Direct3D10Renderer*>(mUIRenderer));
    if (mUIResourceProvider)
        delete mUIResourceProvider;
    mUISystem = nullptr;
    mUIRenderer = nullptr;
    mUIResourceProvider = nullptr;
    mUIWindowManager = nullptr;
    mUISchemeManager = nullptr;
    mUIFontManager = nullptr;
    mErrorTexture = nullptr;
}

ui::Window* CClient::CreateWindow(str type, str name) {
    if (!mUIWindowManager)
        return nullptr;

    ui::String utype = extract<ui::String>(type);
    ui::String uname = extract<ui::String>(name);
    try {
        return mUIWindowManager->createWindow(utype, uname);
    }
    catch (ui::InvalidRequestException&) {
        throw RuntimeError("invalid request, window manager is locked");
    }
    catch (ui::AlreadyExistsException&) {
        throw ValueError("window with name already exists");
    }
    catch (ui::UnknownObjectException&) {
        throw ValueError("no window factory found for type");
    }
}

ui::Window* CClient::GetWindow(str name) const {
    if (!mUIWindowManager)
        throw RuntimeError("can't get window; UI is not initialized");

    ui::String uname = extract<ui::String>(name);
    try {
        return mUIWindowManager->getWindow(uname);
    }
    catch (ui::UnknownObjectException&) {
        throw ValueError(str("Window does not exist: ") + name);
    }
}

bool CClient::IsWindowPresent(str name) const {
    if (!mUIWindowManager)
        return false;

    ui::String uname = extract<ui::String>(name);
    return mUIWindowManager->isWindowPresent(uname);
}

void CClient::DestroyWindow(str name) {
    if (!mUIWindowManager)
        return;

    ui::String uname = extract<ui::String>(name);
    mUIWindowManager->destroyWindow(uname);
}

void CClient::DestroyWindow(ui::Window* w) {
    if (!mUIWindowManager)
        return;

    mUIWindowManager->destroyWindow(w);
}

bool CClient::GetConsoleVisible() const {
    return mConsole ? mConsole->isVisible() : false;
}

void CClient::SetConsoleVisible(bool value) {
    if (mConsole) {
        mConsole->setVisible(value);
        mConsole->activate();
    }
}

//---------------------------
// CClient Renderer implementation
//---------------------------

extern char const* GEffectShaderSource;

void CClient::InitDX() {
    if (mInitedDX)
        return;
    assert(mWindowHandle);
    mLogger->Debug("initializing renderer");

    DXGI_SWAP_CHAIN_DESC scd;
    HRESULT hr;

    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    scd.BufferCount = 1;
    scd.BufferDesc.Width = mViewportSize.Width;
    scd.BufferDesc.Height = mViewportSize.Height;
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    scd.OutputWindow = (HWND)mWindowHandle;
    scd.SampleDesc.Count = 1;
    scd.SampleDesc.Quality = 0;
    scd.Windowed = TRUE;

    UINT flags = D3D10_CREATE_DEVICE_SINGLETHREADED;
#ifdef _DEBUG
    flags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

    hr = D3D10CreateDeviceAndSwapChain1(NULL,
                                        D3D10_DRIVER_TYPE_HARDWARE,
                                        NULL,
                                        flags,
                                        D3D10_FEATURE_LEVEL_10_0,
                                        D3D10_1_SDK_VERSION,
                                        &scd,
                                        &mSwapChain,
                                        &mDevice);
    
    if (FAILED(hr))
        throw RuntimeError("DirectX 10.1 device initialization failed");

    InitRenderTarget();

    SetDXViewport(mViewportSize);

    // Create matrix stack
    hr = D3DXCreateMatrixStack(0, &mViewMatrixStack);
    if (FAILED(hr))
        throw RuntimeError("failed to create view matrix stack");
    hr = D3DXCreateMatrixStack(0, &mProjectionMatrixStack);
    if (FAILED(hr))
        throw RuntimeError("failed to create projecting matrix stack");
    
    // Init rasterizer
    D3D10_RASTERIZER_DESC rd;
    rd.CullMode = D3D10_CULL_NONE;
    rd.FillMode = D3D10_FILL_SOLID;
    rd.FrontCounterClockwise = true;
    rd.DepthBias = false;
    rd.DepthBiasClamp = 0;
    rd.SlopeScaledDepthBias = 0;
    rd.DepthClipEnable = true;
    rd.ScissorEnable = false;
    rd.MultisampleEnable = false;
    rd.AntialiasedLineEnable = true;

    mDevice->CreateRasterizerState(&rd, &mRS);
    mDevice->RSSetState(mRS);

    // Create shader
    mBasicEffect = nullptr;

    intrusive_ptr<core::FFileLoadRequest> lreq(
        new core::FFileLoadRequest("data/shaders/BasicEffect.fx", 0, 0)
        );
    core::GetIOManagerPtr()->ExecuteSL(lreq);
    if (lreq->GetState() == core::IORS_FAILED)
        throw IOError("failed to load basic shader file");

    hr = D3DX10CreateEffectFromMemory((void*)&lreq->GetResult().front(),
                                      lreq->GetResult().size(),
                                      "data/shaders/BasicEffect.fx",
                                      NULL,
                                      NULL,
                                      "fx_4_0",
                                      D3D10_SHADER_ENABLE_STRICTNESS,
                                      0,
                                      mDevice,
                                      NULL,
                                      NULL,
                                      &mBasicEffect,
                                      NULL,
                                      NULL);

    if (FAILED(hr))
        throw RuntimeError("failed to load basic shader effect");

    mFullTechnique = mBasicEffect->GetTechniqueByName("Full");
    mNoTexTechnique = mBasicEffect->GetTechniqueByName("NoTex");
    mWorldMatrixEV = mBasicEffect->GetVariableByName("World")->AsMatrix();
    mProjectionMatrixEV = mBasicEffect->GetVariableByName("Projection")->AsMatrix();
    mViewMatrixEV = mBasicEffect->GetVariableByName("View")->AsMatrix();
    mTextureSR = mBasicEffect->GetVariableByName("Tex2D")->AsShaderResource();
    mGlobalColorEV = mBasicEffect->GetVariableByName("GlobalColor")->AsVector();

    // Create input layout
    D3D10_INPUT_ELEMENT_DESC layout[] = {
        {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0},
        {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0}
    };

    D3D10_PASS_DESC pdesc;
    mFullTechnique->GetPassByIndex(0)->GetDesc(&pdesc);

    hr = mDevice->CreateInputLayout(layout,
                                    sizeof(layout) / sizeof(layout[0]),
                                    pdesc.pIAInputSignature,
                                    pdesc.IAInputSignatureSize,
                                    &mVertexLayout);
    if (FAILED(hr))
        throw RuntimeError("failed to create vertex layout");

    mDevice->IASetInputLayout(mVertexLayout);

    // Create sprite vertex buffer
    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(FVertex) * VERTEX_BUFFER_SIZE;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;

    hr = mDevice->CreateBuffer(&bd,
                               NULL,
                               &mVertexBuffer);
    if (FAILED(hr))
        throw RuntimeError("failed to create sprite vertex buffer");

    mInitedDX = true;
    mLogger->Debug("initialized renderer");
}

void CClient::ExitDX() {
    //if (!mInitedDX)
    //    return;
    mLogger->Debug("exiting DX");
    if (mRendering)
        mLogger->Warning("Exiting DX while using rendering context");
    if (mEditorRenderInfo)
        mEditorRenderInfo.reset();
    ReleaseDX();
    mInitedDX = false;
}

#define RELU(var) \
    do { if (var) { \
        var->Release(); \
        var = nullptr; \
    } } while (0)

void CClient::ReleaseDX() {
    //std::cout << "RELEASING DX OBJECTS" << std::endl;
    ReleaseTileVertexBuffers();
    if (mDevice)
        mDevice->ClearState();
    RELU(mVertexBuffer);
    RELU(mVertexLayout);
    RELU(mBasicEffect);
    RELU(mProjectionMatrixStack);
    RELU(mViewMatrixStack);
    RELU(mRS);
    RELU(mSwapChain);
    RELU(mRTV);
    RELU(mDevice);
}

#define HR(hr_, msg) do { if (FAILED(hr_)) throw RuntimeError(msg); } while (0)
#define HRDX(hr_) do { if (FAILED(hr_)) throw RuntimeError("DX operation failed"); } while (0)

void CClient::InitRenderTarget() {
    ID3D10Texture2D* backBuffer;
    HR(mSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (void**)&backBuffer),
            "failed to get back buffer");
    HR(mDevice->CreateRenderTargetView(backBuffer, NULL, &mRTV),
       "failed to create render target view");
    RELU(backBuffer);

    mDevice->OMSetRenderTargets(1, &mRTV, NULL);
}

void CClient::ResizeDX(FSizeI const& size) {
    assert(mSwapChain);

    // References to back buffers must be released before they can be resized
    if (mRTV) {
        mRTV->Release();
        mRTV = nullptr;
    }

    DXGI_SWAP_CHAIN_DESC scd;
    HR(mSwapChain->GetDesc(&scd), "failed to get swap chain desc");
    HR(mSwapChain->ResizeBuffers(scd.BufferCount,
                                 size.Width,
                                 size.Height,
                                 DXGI_FORMAT_UNKNOWN,
                                 0),
                                 "failed to resize swap chain buffers");

    InitRenderTarget();
    assert(mRTV);

    if (mUIRenderer)
        mUIRenderer->setDisplaySize(size);
}

void CClient::SetDXViewport(FSizeI const& size) {
    assert(mDevice);

    D3D10_VIEWPORT vp;
    ZeroMemory(&vp, sizeof(vp));
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    vp.Width = size.Width;
    vp.Height = size.Height;

    mDevice->RSSetViewports(1, &vp);
}

void CClient::LoadTexture(FTexture& dest, object path) {
    if (!mUIRenderer)
        throw RuntimeError("no renderer to load texture");

    object rpath = GetEngine()->ResolvePath(path);
    if (rpath.is_none())
        throw ValueError(str("LoadTexture() fail, invalid path: ") + str(path));
    //assert(PyUnicode_CheckExact(rpath.ptr()));
    str rpaths = extract<str>(rpath);
    char const* cpath = PyString_AS_STRING(rpaths.ptr());

    ui::Texture* btex = &(mUIRenderer->createTexture(cpath, "none"));

    if (dest.GetIntTexture())
        dest.DestroyIntTexture();
    //dest.SetClient(GetSelfPtr<CClient>());
    dest.SetIntTexture(btex);

    mLoadedTextures.push_back(boost::static_pointer_cast<FTexture>(dest.shared_from_this()));
}

shared_ptr<FTexture> CClient::CreateTexture(str name) {
    if (!mUIRenderer)
        throw RuntimeError("no renderer to create texture");

    shared_ptr<FTexture> tex(new FTexture(name));

    GetEngine()->GetResourceManager()->Register(tex);

    return tex;
}

shared_ptr<FTexture> CClient::CreateTexture(str name, str filename) {
    if (!mUIRenderer)
        throw RuntimeError("no renderer to create texture");

    // Throws exception if name already taken
    shared_ptr<FTexture> tex = CreateTexture(name);

    try {
        LoadTexture(*tex.get(), filename);
    }
    catch (...) {
        DestroyTexture(tex);
        throw;
    }

    assert(tex->GetIsValid() && tex->GetIsLoaded());

    return tex;
}

void CClient::DestroyTexture(shared_ptr<FTexture> texture) {
    assert(texture);
    if (!mUIRenderer)
        return;

    texture->Unload();
    
    GetEngine()->GetResourceManager()->Unregister(texture);
}

shared_ptr<FTexture> CClient::GetTexture(str name, object filename) {
    shared_ptr<FResource> resource = GetEngine()->GetResourceManager()->GetResource(name);
    shared_ptr<FTexture> texture = boost::dynamic_pointer_cast<FTexture>(resource);
    
    if (!texture) {
        if (resource)
            throw RuntimeError(str("non-texture resource exists with name: ") + name);

        if (!filename.is_none())
            texture = CreateTexture(name, extract<str>(filename));
    }

    return texture;
}

ui::Texture* CClient::GetErrorTexture() {
    try {
        if (mErrorTexture == nullptr) {
            mErrorTexture = &mUIRenderer->createTexture();
            uint8 temp_buff[3072];
            uint8* iter = temp_buff;
            bool c = false;
            for (int i = 0; i < 32; i++) {
                c = !c;
                for (int b = 0; b < 32; b++) {
                    c = !c;
                    *iter++ = 254;
                    *iter++ = c? 254: 0;
                    *iter++ = 254;
                }
            }
            mErrorTexture->loadFromMemory(temp_buff,
                                            ui::Size(32, 32),
                                            ui::Texture::PF_RGB);
        }
    }
    catch (ui::Exception& e) {
        mLogger->Error(boost::format("Failed to generate error texture: %1%") % e.getMessage());
        //std::cerr << "! ERROR (engine.cpp line " << __LINE__ << " in " << __FUNCTION__ << ") :" << e.getMessage() << std::endl;
    }
    return mErrorTexture;
}

KH_BEGIN_GC(CClient)
    KH_GC_TPTR(thisx->mCamera)
KH_END_GC()

void _InitClientClasses() {
    _InitClientBindings();
}

void _ExitClientClasses() {
}

char const* GEffectShaderSource =
    "matrix World;\n"
    "matrix View;\n"
    "matrix Projection;\n"
    "\n"
    "struct PS_INPUT\n"
    "{\n"
    "    float4 Pos : SV_POSITION;\n"
    "    float4 Color : COLOR0;\n"
    "};\n"
    "\n"
    "PS_INPUT VS( float4 Pos : POSITION, float4 Color : COLOR )\n"
    "{\n"
    "    PS_INPUT psInput;\n"
    "\n"
    "    Pos = mul( Pos, World );\n"
    "    Pos = mul( Pos, View );\n"
    "\n"
    "    psInput.Pos = mul( Pos, Projection );\n"
    "    psInput.Color = Color;\n"
    "\n"
    "    return psInput;\n"
    "}\n"
    "\n"
    "float4 PS( PS_INPUT psInput ) : SV_Target\n"
    "{\n"
    "    return psInput.Color;\n"
    "}\n"
    "\n"
    "technique10 Render\n"
    "{\n"
    "    pass P0\n"
    "    {\n"
    "        SetVertexShader( CompileShader( vs_4_0, VS() ) );\n"
    "        SetGeometryShader( NULL );\n"
    "        SetPixelShader( CompileShader( ps_4_0, PS() ) );\n"
    "    }\n"
    "}\n"
;

}} // namespace kharlia::engine